# backend/modules/forecasting/routes.py

import os  # 导入os模块，用于文件和路径操作
from flask_restful import Resource, reqparse  # 从flask_restful导入Resource和请求解析器
from flask import  current_app  # 导入当前Flask应用实例
from flask_jwt_extended import jwt_required, get_jwt_identity  # 导入JWT相关模块，用于用户身份验证
from ...extensions import db  # 导入数据库扩展
from .models import Forecast  # 导入Forecast模型，用于保存预测结果
from ...modules.dataimport.models import UploadedFile  # 导入UploadedFile模型，表示上传的文件
from sklearn.ensemble import RandomForestRegressor  # 导入随机森林回归模型
from sklearn.linear_model import LinearRegression  # 导入线性回归模型
import pandas as pd  # 导入pandas，用于数据处理
import json  # 导入json模块，用于数据序列化
from datetime import timedelta  # 从datetime导入timedelta，用于日期时间操作
from ...modules.userinterface.models import DemandForecast  # 导入DemandForecast模型
import pytz  # 导入pytz，用于时区处理
from ..authentication.models import User  # 导入用户模型
from ..visualization.routes import Visualization  # 导入可视化相关路由
from ..userinterface.models import Report  # 导入报告模型

# 定义预测相关的路由
class ForecastingRoutes:
    def __init__(self, api):
        # 将资源添加到API中，配置路由
        api.add_resource(ForecastGenerateAPI, '/api/forecast/generate')  # 添加生成预测的路由
        api.add_resource(ForecastListAPI, '/api/forecast/list')  # 添加预测列表的路由
        api.add_resource(ForecastDeleteAPI, '/api/forecast/delete/<int:forecast_id>')  # 添加删除预测的路由
        api.add_resource(ForecastDetailsAPI, '/api/forecast/details/<int:forecast_id>')  # 添加获取预测详情的路由

# 定义生成预测的API
class ForecastGenerateAPI(Resource):
    @jwt_required()  # 表示此方法需要JWT身份验证
    def post(self):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 根据用户名查询用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        # 如果用户不存在，返回401错误
        if not current_user:
            return {"message": "用户身份验证失败"}, 401

        # 验证用户角色，普通用户和管理员均可访问
        if current_user.role not in ['管理员', '普通用户']:
            return {"message": "未授权访问"}, 403

        # 创建请求解析器，用于解析请求中的JSON参数
        parser = reqparse.RequestParser()
        # 添加模型类型参数
        parser.add_argument('model_type', type=str, required=True, location='json', help="模型类型是必需的")
        # 添加上传文件ID参数
        parser.add_argument('file_id', type=int, required=True, location='json', help="上传文件ID是必需的")
        args = parser.parse_args()  # 解析参数

        model_type = args['model_type']  # 获取模型类型
        file_id = args['file_id']  # 获取上传文件ID

        # 根据文件ID查询上传的文件
        uploaded_file = UploadedFile.query.get(file_id)
        if not uploaded_file:
            return {"message": "上传文件不存在"}, 404  # 文件不存在，返回404错误

        # 权限控制：普通用户只能生成自己的文件的预测，管理员可以生成任何文件的预测
        if current_user.role != '管理员' and uploaded_file.user_id != current_user.id:
            return {"message": "未授权生成其他用户的数据的预测"}, 403

        try:
            # 检查预处理后的文件是否存在
            if not os.path.exists(uploaded_file.preprocessed_path):
                return {"message": "预处理后的文件不存在"}, 404

            # 读取预处理后的CSV文件
            df = pd.read_csv(uploaded_file.preprocessed_path)

            # 添加调试信息
            current_app.logger.debug("CSV 文件列名: %s", df.columns.tolist())  # 输出CSV文件的列名
            current_app.logger.debug("CSV 文件预览:\n%s", df.head())  # 输出CSV文件的预览

            # 确保必要的列存在
            required_columns = ['当前订单数量', '当前库存数量', '历史补货数量', '年', '月', '日', '星期', '品类', '日期']
            for col in required_columns:
                if col not in df.columns:
                    return {"message": f"缺少必要的列: '{col}'"}, 400  # 如果缺少必要列，返回400错误

            # 确保没有缺失值
            if df[required_columns].isnull().any().any():
                return {"message": "上传的数据存在缺失值，请检查后重试。"}, 400  # 如果存在缺失值，返回400错误

            # 转换 '日期' 列为 datetime，并设置为北京时间
            df['日期'] = pd.to_datetime(df['日期'], errors='coerce', utc=True)
            if df['日期'].isnull().any():
                return {"message": "日期列存在无效日期格式"}, 400  # 如果日期列有无效格式，返回400错误

            # 转换为北京时间
            beijing_tz = pytz.timezone('Asia/Shanghai')
            df['日期'] = df['日期'].dt.tz_convert(beijing_tz)  # 将日期转换为北京时间

            # 确保 '日期' 列被正确转换
            current_app.logger.debug("转换后的 '日期' 列类型: %s", df['日期'].dtype)  # 输出日期列的数据类型
            current_app.logger.debug("转换后的 '日期' 列预览:\n%s", df['日期'].head())  # 输出转换后的日期列预览

            # 特征工程
            # 已在预处理阶段完成：年、月、日、星期

            # 获取所有品类
            categories = df['品类'].unique()  # 获取品类的唯一值
            forecasts = []  # 用于存储所有的预测结果

            # 遍历每一个品类进行预测
            for category in categories:
                current_app.logger.info(f"开始预测品类: {category}")  # 输出当前预测的品类

                df_cat = df[df['品类'] == category]  # 获取当前品类的数据

                # 如果当前品类没有数据，跳过预测
                if df_cat.empty:
                    current_app.logger.warning(f"品类 {category} 没有数据，跳过预测。")
                    continue

                # 选择特征和目标变量
                X = df_cat[['历史补货数量', '年', '月', '日', '星期']]
                y_demand = df_cat['当前订单数量']  # 目标变量：当前订单数量
                y_inventory = df_cat['当前库存数量']  # 另一目标变量：当前库存数量

                # 根据模型类型选择模型
                if model_type == 'RandomForest':
                    model_demand = RandomForestRegressor(random_state=42)  # 随机森林模型用于需求预测
                    model_inventory = RandomForestRegressor(random_state=42)  # 随机森林模型用于库存预测
                elif model_type == 'LinearRegression':
                    model_demand = LinearRegression()  # 线性回归模型用于需求预测
                    model_inventory = LinearRegression()  # 线性回归模型用于库存预测
                else:
                    return {"message": "无效的模型类型"}, 400  # 如果模型类型无效，返回400错误

                # 训练模型
                model_demand.fit(X, y_demand)  # 训练需求预测模型
                model_inventory.fit(X, y_inventory)  # 训练库存预测模型

                # 生成未来一年的预测（按天预测）
                future_days = 365  # 未来预测天数
                last_input_date = df_cat['日期'].max()  # 获取当前品类的最新日期

                if pd.isnull(last_input_date):
                    return {"message": f"品类 {category} 无法确定开始日期"}, 400  # 如果无法确定开始日期，返回400错误

                # 确保 last_input_date 为 datetime 对象并设置为北京时间
                if not isinstance(last_input_date, pd.Timestamp):
                    return {"message": f"品类 {category} 日期列转换失败"}, 400  # 如果转换失败，返回400错误

                # 确保 last_input_date 已有时区信息
                if last_input_date.tzinfo is None:
                    last_input_date = beijing_tz.localize(last_input_date)  # 如果没有时区信息，则添加时区
                else:
                    last_input_date = last_input_date.astimezone(beijing_tz)  # 转换为北京时间

                current_app.logger.debug(f"开始日期 (北京时间) for {category}: {last_input_date}")  # 输出开始日期

                # 记录最后一个日期
                last_date = last_input_date.date()

                # 生成未来日期列表
                forecast_dates = [last_input_date + timedelta(days=i+1) for i in range(future_days)]  # 未来日期
                forecast_dates_str = [date.strftime('%Y-%m-%d') for date in forecast_dates]  # 格式化日期为字符串

                # 生成特征数据
                future_features = pd.DataFrame({
                    '历史补货数量': [df_cat['历史补货数量'].mean()] * future_days,  # 使用平均历史补货数量
                    '年': [date.year for date in forecast_dates],
                    '月': [date.month for date in forecast_dates],
                    '日': [date.day for date in forecast_dates],
                    '星期': [date.weekday() for date in forecast_dates]
                })

                # 预测需求和库存
                future_predictions_demand = model_demand.predict(future_features).astype(int).tolist()  # 预测需求
                future_predictions_inventory = model_inventory.predict(future_features).astype(int).tolist()  # 预测库存

                # 库存补货建议
                inventory_suggestions = [
                    {
                        "date": date_str,
                        "forecast_demand": demand,
                        "current_inventory": inventory,
                        "reorder_quantity": int(round(demand * 0.8))  # 补货量为需求量的80%，转换为整数
                    }
                    for date_str, demand, inventory in zip(forecast_dates_str, future_predictions_demand, future_predictions_inventory)
                ]

                # 保存预测结果到 Forecast 模型
                forecast = Forecast(
                    model_type=model_type,  # 存储模型类型
                    result=json.dumps(future_predictions_demand, ensure_ascii=False),  # 存储预测需求结果
                    inventory_suggestions=json.dumps(inventory_suggestions, ensure_ascii=False),  # 存储库存补货建议
                    prediction_period=future_days,  # 存储预测天数
                    uploaded_file_id=file_id,  # 存储上传文件ID
                    category=category,  # 存储品类
                    last_date=last_date  # 设置最后一个输入数据的日期
                )
                db.session.add(forecast)  # 将预测结果添加到数据库会话
                db.session.commit()  # 提交以获取forecast.id

                # 创建 DemandForecast 条目并关联到 Forecast
                for date_str, quantity in zip(forecast_dates_str, future_predictions_demand):
                    demand_forecast = DemandForecast(
                        product_name=category,  # 设置品类名称
                        forecast_quantity=int(quantity),  # 确保是整数
                        forecast_date=date_str,  # 设置预测日期
                        forecast_id=forecast.id  # 关联到 Forecast
                    )
                    db.session.add(demand_forecast)  # 将需求预测添加到数据库会话

                # 提交所有 DemandForecast 条目
                db.session.commit()

                forecasts.append(forecast)  # 将结果添加到forecast列表

            if not forecasts:
                return {"message": "没有可生成的预测记录。"}, 400  # 如果没有预测记录，返回400错误

            return {
                "message": "预测生成成功",
                "forecast_ids": [forecast.id for forecast in forecasts]  # 返回生成的预测ID
            }, 200
        except Exception as e:
            current_app.logger.error(f"生成预测失败: {e}")  # 记录错误日志

# 定义获取预测列表的API
class ForecastListAPI(Resource):
    @jwt_required()  # 表示此方法需要JWT身份验证
    def get(self):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 根据用户名查询用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        # 如果用户不存在，返回401错误
        if not current_user:
            return {"message": "用户身份验证失败"}, 401

        try:
            # 如果是普通用户，获取与用户相关的上传文件ID
            if current_user.role != '管理员':
                user_file_ids = [file.id for file in current_user.uploaded_files]  # 获取用户上传的文件ID
                forecasts = Forecast.query.filter(Forecast.uploaded_file_id.in_(user_file_ids)).order_by(Forecast.timestamp.desc()).all()  # 查询用户相关的预测
            else:
                forecasts = Forecast.query.order_by(Forecast.timestamp.desc()).all()  # 管理员获取所有预测

            forecast_list = [
                {
                    "id": forecast.id,  # 预测ID
                    "timestamp": forecast.timestamp.strftime('%Y-%m-%d %H:%M:%S'),  # 预测时间戳
                    "file_name": forecast.uploaded_file.file_name if forecast.uploaded_file else "未知",  # 上传文件名称
                    "category": forecast.category  # 预测品类
                }
                for forecast in forecasts
            ]
            return {"forecasts": forecast_list}, 200  # 返回预测列表
        except Exception as e:
            current_app.logger.error(f"获取预测列表失败: {e}")  # 记录错误日志
            return {"message": "获取预测列表失败，请重试。"}, 500  # 返回500错误

# 定义删除预测的API
class ForecastDeleteAPI(Resource):
    @jwt_required()  # 表示此方法需要JWT身份验证
    def delete(self, forecast_id):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 根据用户名查询用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        # 如果用户不存在，返回401错误
        if not current_user:
            return {"message": "用户身份验证失败"}, 401

        # 根据预测ID获取预测记录
        forecast = Forecast.query.get(forecast_id)
        if not forecast:
            return {"message": "预测记录不存在"}, 404  # 预测不存在，返回404错误

        # 获取预测记录的上传用户
        uploaded_file = UploadedFile.query.get(forecast.uploaded_file_id)
        if not uploaded_file:
            return {"message": "关联的上传文件不存在"}, 404  # 关联的上传文件不存在，返回404错误

        target_user = uploaded_file.user if uploaded_file.user else None  # 获取上传文件的用户
        if not target_user:
            return {"message": "关联的用户不存在"}, 404  # 关联的用户不存在，返回404错误

        # 权限控制
        if current_user.role == '管理员':
            if target_user.role == '管理员' and not current_user.is_pzy():
                return {"message": "您无权删除其他管理员的数据"}, 403  # 管理员不能删除其他管理员的数据
            # 管理员可以删除普通用户的数据或pzy管理员的数据
        elif forecast.uploaded_file.user_id == current_user.id:
            # 普通用户可以删除自己的预测记录
            pass
        else:
            return {"message": "未授权删除该预测记录"}, 403  # 无权删除，返回403错误

        try:
            # 删除关联的需求预测记录
            demand_forecasts = DemandForecast.query.filter_by(forecast_id=forecast_id).all()
            for df in demand_forecasts:
                db.session.delete(df)  # 从会话中删除需求预测

            # 删除关联的库存管理记录（如果有）
            # 如果库存管理记录只与上传文件关联，不需要在这里删除

            # 删除关联的可视化记录
            visualizations = Visualization.query.filter_by(forecast_id=forecast_id).all()
            for viz in visualizations:
                # 删除图表文件
                chart_file_path = os.path.join(current_app.root_path, 'static', viz.image_path)
                if os.path.exists(chart_file_path):
                    os.remove(chart_file_path)  # 删除图表文件
                db.session.delete(viz)  # 从会话中删除可视化记录

            # 删除关联的报告记录
            reports = Report.query.filter_by(forecast_id=forecast_id).all()
            for report in reports:
                # 删除报告文件
                if os.path.exists(report.filepath):
                    os.remove(report.filepath)  # 删除报告文件
                db.session.delete(report)  # 从会话中删除报告记录

            # 最后删除预测记录
            db.session.delete(forecast)  # 从会话中删除预测记录
            db.session.commit()  # 提交事务
            return {"message": "预测记录及相关数据已删除"}, 200  # 返回成功消息
        except Exception as e:
            db.session.rollback()  # 如果出现错误，回滚事务
            current_app.logger.error(f"删除预测记录失败: {e}", exc_info=True)  # 记录错误日志
            return {"message": "删除预测记录失败，请重试。"}, 500  # 返回500错误

# 定义获取预测详情的API
class ForecastDetailsAPI(Resource):
    @jwt_required()  # 表示此方法需要JWT身份验证
    def get(self, forecast_id):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 根据用户名查询用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        # 如果用户不存在，返回401错误
        if not current_user:
            return {"message": "用户身份验证失败"}, 401

        # 根据预测ID获取预测记录
        forecast = Forecast.query.get(forecast_id)
        if not forecast:
            return {"message": "预测记录不存在"}, 404  # 预测不存在，返回404错误

        # 权限控制：管理员可以查看所有，普通用户只能查看自己的
        if current_user.role != '管理员' and forecast.uploaded_file.user_id != current_user.id:
            return {"message": "未授权访问"}, 403  # 无权访问，返回403错误

        try:
            # 获取DemandForecast和库存建议
            demand_forecasts = DemandForecast.query.filter_by(forecast_id=forecast_id).all()  # 查询需求预测记录
            inventory_suggestions = json.loads(forecast.inventory_suggestions) if forecast.inventory_suggestions else []  # 获取库存建议

            demand_forecast_list = [  # 构建需求预测列表
                {
                    "forecast_date": df.forecast_date.strftime('%Y-%m-%d'),  # 预测日期
                    "forecast_quantity": df.forecast_quantity  # 预测数量
                }
                for df in demand_forecasts
            ]

            return {
                "id": forecast.id,  # 返回预测ID
                "model_type": forecast.model_type,  # 返回模型类型
                "category": forecast.category,  # 返回品类
                "demand_forecasts": demand_forecast_list,  # 返回需求预测列表
                "inventory_suggestions": inventory_suggestions  # 返回库存建议
            }, 200
        except Exception as e:
            current_app.logger.error(f"获取预测详细信息失败: {e}")  # 记录错误日志
            return {"message": "获取预测详细信息失败，请重试。"}, 500  # 返回500错误
